If you are maintaining a cross-platform SDK, you must know how C++ and Java interact. As a C++ engineer, I just learnt how to use JNI to accompalish work formerly, like how C++ access Java or how Java access C++, actually I didn’t know why. I am full of questions. For example,
Why native interfaces should be named and exported as packagename_classname_functionname?
Why JNI_OnLoad will be called when dynamic lib is loaded?
What have been done when we call AttachCurrentThread or DetachCurrentThread?
What are Local Reference and Global Reference? how do they work?
What is JavaVM? What is JNIEnv?
What’s the relationship between JVM and JNI? …
This article will answer these doubts and give you a clearer understanding of JNI and JVM.
Android JVM evolution history
When we talk about JVM(java virtual machine), GC(Garbage Collection) is always mentioned firstly, yes, GC helps software engineers free from heap memory mallocing and relesing, so we love it! But, the original intention, java program language is designed for cross-platform development. Different CPUs have dirrerent set of instructions, we need to develop corresponding compiler to compile C++, it is a big job. Therefor, Java was born, java code is compiled to bytecode, different platforms can interpret these byte code to corresponding instructions and perform these translated instructions. Because Java interpreter is much lighter than C++ compiler, so more and more architectures support Java language.
Java interoreter’s performance is lower than C++ compiled native code. Although Java can easily compatible with niche platforms, If java wants to gain a foothold in the major platforms, ex. Android, optimization is needed.
JVM evolution history of Android is constantly adjust the design of compiling byte code to native code. On the one hand, JVM should play the role of compiler, to compile hot code to native code to imporve performance. On the other hand, JVM should find the balance between performance, delay, storage, and power consumption.
Android version
Modificatons
advantages
disadvantages
Formats
Android 2.2
Dalvik, Support JIT(Just In Time)
Hot code is compiled at app running time.
No cache, every startup requires recompilation, high power consumption.
.odex
Android 4.4
ART, Support AOT(Ahead Of Time)
Compiled once when app is installing.
Take more time to install and take up more storage.
.oat
Android 7.0
ART, Support JIT+AOT(All Of the Time compilation)
Compileing hot code when your device is in idle or charging state.
Performs poorly when first run.
.oat
Dalvik and ART are all android virtual machines, Dalvik interprets .dex file to native code to perform, JIT hleps to compile hot code to native code to improve performance. ART(android runtime) performs native code (.oat file) which is compiled from .dex files when app is installed.
Dex file is compiled from java resources, which is an intermediate byte code format. Android app contains more than one dex file, these dex files will be loaded and interpreted as needed. Details refer to: https://source.android.com/docs/core/runtime/dex-format
Loading classes form dex files is inefficient, oat is ELF file, whitch contains all dex files and compiled native code. ART performs native code firstly, if code haven’t been compiled to native code, ART will perform dex byte code by interpreter.
After android 7.0, ART changes the compiling strategy, instead of Head Of Compiling(compiling in installing stage), All Of the Time compilation is more reasonable. After app installation, ART interprets dex at first running time (performing byte code), then ART will trace hot code and compiling to native code asynchronously via JIT, ART will perform compiled native code at next running time. Details refer to:https://source.android.com/docs/core/runtime/jit-compiler
After android 8.0, ART add vdex/art file type, vdex file is splited from oat file and contains uncompressed dex files and metadata, it helps to avoid dex files unpacking and verifing when android system is updated. art file is memory image which caches hot objects like ArtField/ArtMethod/DexCheche/ClassTable/…, ART will map art file to structured memory when app startup, it helps to speed up class loading from dex file or oat file.
File Format
Description
.dex
java byte code
.odex/.oat
optimized dex, ELF format
.vdex
verified dex, contains raw dex and quicken info
.art
image file, cached hot objects like string/method/type/…
Android Zygote
When is the JVM loaded? Does every android app need load JVM? Android system plays a trick, Android disigns a prcocess, which is responsible for loading resoures that is vital to an android app, for example, Jave FrameWork, JVM and JNI functions. This process is Zygote, almost every user process in android system is forked by Zygote process. Since Zygote has helped with initialization, forked prcesses donot need do it again. The image below describes Android system’s starting up:
Init process is the first user process started by kernel, and is the father of all the user processes. Init Process will start Zygote process when loading init.rc file. Zygote will start system servers, such as audioserver\cameraserver\meida\netd..., details as below:
\system\core\rootdir\init.zygote64.rc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server --socket-name=zygote class main priority -20 user root group root readproc reserved_disk socket zygote stream 660 root system socket usap_pool_primary stream 660 root system onrestart exec_background - systemsystem-- /system/bin/vdc volume abort_fuse onrestart write /sys/power/state on onrestart restart audioserver onrestart restart cameraserver onrestart restart media onrestart restart media.tuner onrestart restart netd onrestart restart wificond task_profiles ProcessCapacityHigh MaxPerformance critical window=${zygote.critical_window.minute:-off} target=zygote-fatal
These service-servers help Zygote and its forked processes to access io devices, this stripped out modular design makes zygote’s responsibilities more simple.
In addition, Zygote shared RAM pages across all app processes which forked by Zygote to reduce memory usage. Static data is mmapped into a process. This not only allows that same data to be shared between processes but also allows it to be paged out when needed. eg. Dalvik code (pre-linked .odex file), app resources, native code in .so files.
App needs to start activity and load resources(eg. dex and so) of himself when it is forked by Zygote, Zygote and JVM are all C++ code, resources finally will be stored in structured memories which are C++ objects, for example, share libraries will be load to SharedLibrary object stored in an array, java classes will be load to mirror::Class object stored in ClassTable, java class’s methods/fields are loaded to ArtMethod/ArtField objects stored in mirror::Class.
ART JVM source code path:
\art\runtime\
Register Java native function
JNI(Java Native Interface) is an interface which helps Java interactives with native code (Probably compiled from C++/C code).
Java access C++ code, two ways:
Static registation: dynamic libraries expose native interfaces according to specified rules. ART will search these exposed interfaces and update native interfaces’ addresses to ArtMethod objects by calling ClassLinker::RegisterNative when loading java class to memory.
Dynamic registation: dynamic libraries register native interface actively by calling JNIEnv::RegisterNatives in JNI_OnLoad function which will be called when loading dynamic libraries, JNIEnv::RegisterNatives will update native interfaces’ addresses to ArtMethod object.
publicclassSimpleJNIextendsActivity { /** Called when the activity is first created. */ @Override publicvoidonCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); TextViewtv=newTextView(this); intsum= Native.add(2, 3); tv.setText("2 + 3 = " + Integer.toString(sum)); setContentView(tv); } }
classNative { static { // The runtime will add "lib" on the front and ".o" on the end of // the name supplied to loadLibrary. System.loadLibrary("simplejni"); }
privatesynchronizedvoidloadLibrary0(ClassLoader loader, Class<?> callerClass, String libname) { // // 'libname' which is the input parameter of System.loadLibrary function shoud be the middle name of your library. // If your lib name is libTest.so, 'libname' parameter should be "Test", if you input "libTest.so" as the parameter, stupid code will search "liblibTest.so.so". // if (libname.indexOf((int)File.separatorChar) != -1) { thrownewUnsatisfiedLinkError( "Directory separator should not appear in library name: " + libname); }
StringlibraryName= libname; if (loader != null && !(loader instanceof BootClassLoader)) { Stringfilename= loader.findLibrary(libraryName); if (filename == null && (loader.getClass() == PathClassLoader.class || loader.getClass() == DelegateLastClassLoader.class)) { // // Don't give up even if we failed to find the library in the native lib paths. // The underlying dynamic linker might be able to find the lib in one of the linker // namespaces associated with the current linker namespace. // System.mapLibraryName("Test") return "libTest.so", this function do nothing but return the full library name. // filename = System.mapLibraryName(libraryName); } if (filename == null) { thrownewUnsatisfiedLinkError(loader + " couldn't find \"" + System.mapLibraryName(libraryName) + "\""); }
// // nativeLoad is actually a native function, which does two things: // 1) Load library to VM. // 2) Call JNI_OnLoad function in your library. // getLibPaths(); Stringfilename= System.mapLibraryName(libraryName); Stringerror= nativeLoad(filename, loader, callerClass); if (error != null) { thrownewUnsatisfiedLinkError(error); } }
// Don't let a pending exception from JNI_OnLoad cause a CheckJNI issue with NewStringUTF. env->ExceptionClear(); return env->NewStringUTF(error_msg.c_str()); }
boolJavaVMExt::LoadNativeLibrary(JNIEnv* env, const std::string& path, jobject class_loader, jclass caller_class, std::string* error_msg){ ... // See if we've already loaded this library. If we have, and the class loader // matches, return successfully without doing anything. // TODO: for better results we should canonicalize the pathname (or even compare // inodes). This implementation is fine if everybody is using System.loadLibrary. SharedLibrary* library; Thread* self = Thread::Current(); .... // Create a new entry. // TODO: move the locking (and more of this logic) into Libraries. bool created_library = false; { // Create SharedLibrary ahead of taking the libraries lock to maintain lock ordering. std::unique_ptr<SharedLibrary> new_library( new SharedLibrary(env, self, path, handle, needs_native_bridge, class_loader, class_loader_allocator));
MutexLock mu(self, *Locks::jni_libraries_lock_); library = libraries_->Get(path); if (library == nullptr) { // We won race to get libraries_lock. library = new_library.release(); libraries_->Put(path, library); created_library = true; } } if (!created_library) { LOG(INFO) << "WOW: we lost a race to add shared library: " << "\"" << path << "\" ClassLoader=" << class_loader; return library->CheckOnLoadResult(); } VLOG(jni) << "[Added shared library \"" << path << "\" for ClassLoader " << class_loader << "]";
bool was_successful = false; void* sym = library->FindSymbol("JNI_OnLoad", nullptr); if (sym == nullptr) { VLOG(jni) << "[No JNI_OnLoad found in \"" << path << "\"]"; was_successful = true; } else { // Call JNI_OnLoad. We have to override the current class // loader, which will always be "null" since the stuff at the // top of the stack is around Runtime.loadLibrary(). (See // the comments in the JNI FindClass function.) ScopedLocalRef<jobject> old_class_loader(env, env->NewLocalRef(self->GetClassLoaderOverride())); self->SetClassLoaderOverride(class_loader);
VLOG(jni) << "[Calling JNI_OnLoad in \"" << path << "\"]"; using JNI_OnLoadFn = int(*)(JavaVM*, void*); JNI_OnLoadFn jni_on_load = reinterpret_cast<JNI_OnLoadFn>(sym); int version = (*jni_on_load)(this, nullptr);
// DexPathList#DexPathList construction function DexPathList(ClassLoader definingContext, String dexPath, String librarySearchPath, File optimizedDirectory, boolean isTrusted) { ...
// Native libraries may exist in both the system and // application library paths, and we use this search order: // // 1. This class loader's library path for application libraries (librarySearchPath): // 1.1. Native library directories // 1.2. Path to libraries in apk-files // 2. The VM's library path from the system property for system libraries // also known as java.library.path // // This order was reversed prior to Gingerbread; see http://b/2933456. this.nativeLibraryDirectories = splitPaths(librarySearchPath, false); this.systemNativeLibraryDirectories = splitPaths(System.getProperty("java.library.path"), true); this.nativeLibraryPathElements = makePathElements(getAllNativeLibraryDirectories());
... }
// DexPathList#addNativePath publicvoidaddNativePath(Collection<String> libPaths) { if (libPaths.isEmpty()) { return; } List<File> libFiles = newArrayList<>(libPaths.size()); for (String path : libPaths) { libFiles.add(newFile(path)); } ArrayList<NativeLibraryElement> newPaths = newArrayList<>(nativeLibraryPathElements.length + libPaths.size()); newPaths.addAll(Arrays.asList(nativeLibraryPathElements)); for (NativeLibraryElement element : makePathElements(libFiles)) { if (!newPaths.contains(element)) { newPaths.add(element); } } nativeLibraryPathElements = newPaths.toArray(newNativeLibraryElement[newPaths.size()]); }
if (isBundledApp) { libraryPermittedPath += File.pathSeparator + Paths.get(getAppDir()).getParent().toString();
// This is necessary to grant bundled apps access to // libraries located in subdirectories of /system/lib libraryPermittedPath += File.pathSeparator + defaultSearchPaths; }
finalStringlibrarySearchPath= TextUtils.join(File.pathSeparator, libPaths); ... if (!libPaths.isEmpty()) { // Temporarily disable logging of disk reads on the Looper thread as this is necessary StrictMode.ThreadPolicyoldPolicy= allowThreadDiskReads(); try { ApplicationLoaders.getDefault().addNative(mDefaultClassLoader, libPaths); } finally { setThreadPolicy(oldPolicy); } } ... }
// Do not load all available splits if the app requested isolated split loading. if (aInfo.splitSourceDirs != null && !aInfo.requestsIsolatedSplitLoading()) { Collections.addAll(outZipPaths, aInfo.splitSourceDirs); }
if (appDir.equals(instrumentationAppDir) || appDir.equals(instrumentedAppDir)) { outZipPaths.clear(); outZipPaths.add(instrumentationAppDir); if (!instrumentationAppDir.equals(instrumentedAppDir)) { outZipPaths.add(instrumentedAppDir); }
// Only add splits if the app did not request isolated split loading. if (!aInfo.requestsIsolatedSplitLoading()) { if (instrumentationSplitAppDirs != null) { Collections.addAll(outZipPaths, instrumentationSplitAppDirs); }
if (!instrumentationAppDir.equals(instrumentedAppDir)) { if (instrumentedSplitAppDirs != null) { Collections.addAll(outZipPaths, instrumentedSplitAppDirs); } } }
if (outLibPaths != null) { outLibPaths.add(instrumentationLibDir); if (!instrumentationLibDir.equals(instrumentedLibDir)) { outLibPaths.add(instrumentedLibDir); } }
if (!instrumentedAppDir.equals(instrumentationAppDir)) { instrumentationLibs = getLibrariesFor(instrumentationPackageName); } } }
if (outLibPaths != null) { if (outLibPaths.isEmpty()) { outLibPaths.add(libDir); }
// Add path to libraries in apk for current abi. Do this now because more entries // will be added to zipPaths that shouldn't be part of the library path. if (aInfo.primaryCpuAbi != null) { // Add fake libs into the library search path if we target prior to N. if (aInfo.targetSdkVersion < Build.VERSION_CODES.N) { outLibPaths.add("/system/fake-libs" + (VMRuntime.is64BitAbi(aInfo.primaryCpuAbi) ? "64" : "")); } for (String apk : outZipPaths) { outLibPaths.add(apk + "!/lib/" + aInfo.primaryCpuAbi); } }
if (isBundledApp) { // Add path to system libraries to libPaths; // Access to system libs should be limited // to bundled applications; this is why updated // system apps are not included. outLibPaths.add(System.getProperty("java.library.path")); } }
// Add the shared libraries native paths. The dex files in shared libraries will // be resolved through shared library loaders, which are setup later. Set<String> outSeenPaths = newLinkedHashSet<>(); appendSharedLibrariesLibPathsIfNeeded( aInfo.sharedLibraryInfos, aInfo, outSeenPaths, outLibPaths);
// ApplicationInfo.sharedLibraryFiles is a public API, so anyone can change it. // We prepend shared libraries that the package manager hasn't seen, maintaining their // original order where possible. if (aInfo.sharedLibraryFiles != null) { intindex=0; for (String lib : aInfo.sharedLibraryFiles) { // sharedLibraryFiles might contain native shared libraries that are not APK paths. if (!lib.endsWith(".apk")) { continue; } if (!outSeenPaths.contains(lib) && !outZipPaths.contains(lib)) { outZipPaths.add(index, lib); index++; appendApkLibPathIfNeeded(lib, aInfo, outLibPaths); } } }
if (instrumentationLibs != null) { for (String lib : instrumentationLibs) { if (!outZipPaths.contains(lib)) { outZipPaths.add(0, lib); appendApkLibPathIfNeeded(lib, aInfo, outLibPaths); } } } }
// LoadedApk#appendApkLibPathIfNeeded privatestaticvoidappendApkLibPathIfNeeded(@NonNull String path, @NonNull ApplicationInfo applicationInfo, @Nullable List<String> outLibPaths) { // Looking at the suffix is a little hacky but a safe and simple solution. // We will be revisiting code in the next release and clean this up. if (outLibPaths != null && applicationInfo.primaryCpuAbi != null && path.endsWith(".apk")) { if (applicationInfo.targetSdkVersion >= Build.VERSION_CODES.O) { outLibPaths.add(path + "!/lib/" + applicationInfo.primaryCpuAbi); } } }
ClassLoader.findLibrary will search the paths of native library and return the full path of your library. Many candidate paths will be genarated when App was loaded, as below:
System.getProperty(“java.library.path”), return System default library paths, for example, “/system/lib:/vendor/lib:/product/lib”
apkPath + “!/lib/“ + primaryCpuAbi, for example, “/data/app/[package_name]!/lib/armeabli-v7a”
SystemProperties.get(“ro.dalvik.vm.isa.” + secondaryCpuAbi), for example, “/data/app/[package_name]/lib/arm”
Start Zygote and JVM
We already know how dynamic is loaded, if you look at the calling code, you will find that Java loads dynamic library by calling Runtime_nativeLoad which is in Runtime.c file. Runtime_nativeLoad is a native interface, who loads the dynamic library which contains Runtime_nativeLoad function?
Let’s firstly find out that which library contains Runtime.c, we find it is in libopenjdk.so by search bp file.
std::string bootscript = GetProperty("ro.boot.init_rc", ""); if (bootscript.empty()) { parser.ParseConfig("/system/etc/init/hw/init.rc"); if (!parser.ParseConfig("/system/etc/init")) { late_import_paths.emplace_back("/system/etc/init"); } // late_import is available only in Q and earlier release. As we don't // have system_ext in those versions, skip late_import for system_ext. parser.ParseConfig("/system_ext/etc/init"); if (!parser.ParseConfig("/vendor/etc/init")) { late_import_paths.emplace_back("/vendor/etc/init"); } if (!parser.ParseConfig("/odm/etc/init")) { late_import_paths.emplace_back("/odm/etc/init"); } if (!parser.ParseConfig("/product/etc/init")) { late_import_paths.emplace_back("/product/etc/init"); } } else { parser.ParseConfig(bootscript); } }
if (zygote) { runtime.start("com.android.internal.os.ZygoteInit", args, zygote); } elseif (!className.isEmpty()) { runtime.start("com.android.internal.os.RuntimeInit", args, zygote); } else { fprintf(stderr, "Error: no class name or --zygote supplied.\n"); app_usage(); LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied."); } }
/* * Register android functions. */ if (startReg(env) < 0) { ALOGE("Unable to register all android natives\n"); return; }
...
/* * Start VM. This thread becomes the main thread of the VM, and will * not return until the VM exits. */ char* slashClassName = toSlashClassName(className != NULL ? className : ""); jclass startClass = env->FindClass(slashClassName); if (startClass == NULL) { ALOGE("JavaVM unable to locate class '%s'\n", slashClassName); /* keep going */ } else { jmethodID startMeth = env->GetStaticMethodID(startClass, "main", "([Ljava/lang/String;)V"); if (startMeth == NULL) { ALOGE("JavaVM unable to find main() in '%s'\n", className); /* keep going */ } else { env->CallStaticVoidMethod(startClass, startMeth, strArray);
#if 0 if (env->ExceptionCheck()) threadExitUncaughtException(env); #endif } } free(slashClassName);
ALOGD("Shutting down VM\n"); if (mJavaVM->DetachCurrentThread() != JNI_OK) ALOGW("Warning: unable to detach main thread\n"); if (mJavaVM->DestroyJavaVM() != 0) ALOGW("Warning: VM did not shut down cleanly\n"); }
/* * Initialize the VM. * * The JavaVM* is essentially per-process, and the JNIEnv* is per-thread. * If this call succeeds, the VM is ready, and we can start issuing * JNI calls. */ if (JNI_CreateJavaVM(pJavaVM, pEnv, &initArgs) < 0) { ALOGE("JNI_CreateJavaVM failed\n"); return-1; }
extern"C"jint JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args){ ScopedTrace trace(__FUNCTION__); const JavaVMInitArgs* args = static_cast<JavaVMInitArgs*>(vm_args); if (JavaVMExt::IsBadJniVersion(args->version)) { LOG(ERROR) << "Bad JNI version passed to CreateJavaVM: " << args->version; return JNI_EVERSION; } RuntimeOptions options; for (int i = 0; i < args->nOptions; ++i) { JavaVMOption* option = &args->options[i]; options.push_back(std::make_pair(std::string(option->optionString), option->extraInfo)); } bool ignore_unrecognized = args->ignoreUnrecognized; if (!Runtime::Create(options, ignore_unrecognized)) { return JNI_ERR; }
// When `ART_CRASH_RUNTIME_DELIBERATELY` is defined (which happens only in the // case of a test APEX), we crash the runtime here on purpose, to test the // behavior of rollbacks following a failed ART Mainline Module update. #ifdef ART_CRASH_RUNTIME_DELIBERATELY LOG(FATAL) << "Runtime crashing deliberately for testing purposes."; #endif
// Initialize native loader. This step makes sure we have // everything set up before we start using JNI. android::InitializeNativeLoader();
// InitNativeMethods needs to be after started_ so that the classes // it touches will have methods linked to the oat file if necessary. { ScopedTrace trace2("InitNativeMethods"); InitNativeMethods(); } ... }
// Must be in the kNative state for calling native methods (JNI_OnLoad code). CHECK_EQ(self->GetState(), ThreadState::kNative);
// Set up the native methods provided by the runtime itself. RegisterRuntimeNativeMethods(env);
// Initialize classes used in JNI. The initialization requires runtime native // methods to be loaded first. WellKnownClasses::Init(env);
// Then set up libjavacore / libopenjdk / libicu_jni ,which are just // a regular JNI libraries with a regular JNI_OnLoad. Most JNI libraries can // just use System.loadLibrary, but libcore can't because it's the library // that implements System.loadLibrary! // // By setting calling class to java.lang.Object, the caller location for these // JNI libs is core-oj.jar in the ART APEX, and hence they are loaded from the // com_android_art linker namespace.
// libicu_jni has to be initialized before libopenjdk{d} due to runtime dependency from // libopenjdk{d} to Icu4cMetadata native methods in libicu_jni. See http://b/143888405 { std::string error_msg; if (!java_vm_->LoadNativeLibrary( env, "libicu_jni.so", nullptr, WellKnownClasses::java_lang_Object, &error_msg)) { LOG(FATAL) << "LoadNativeLibrary failed for \"libicu_jni.so\": " << error_msg; } } { std::string error_msg; if (!java_vm_->LoadNativeLibrary( env, "libjavacore.so", nullptr, WellKnownClasses::java_lang_Object, &error_msg)) { LOG(FATAL) << "LoadNativeLibrary failed for \"libjavacore.so\": " << error_msg; } } { constexprconstchar* kOpenJdkLibrary = kIsDebugBuild ? "libopenjdkd.so" : "libopenjdk.so"; std::string error_msg; if (!java_vm_->LoadNativeLibrary( env, kOpenJdkLibrary, nullptr, WellKnownClasses::java_lang_Object, &error_msg)) { LOG(FATAL) << "LoadNativeLibrary failed for \"" << kOpenJdkLibrary << "\": " << error_msg; } }
// Initialize well known classes that may invoke runtime native methods. WellKnownClasses::LateInit(env);
Now we find Zygote call system API to load libart.so which implements JVM and then call libart.so::JNI_CreateJavaVM to create JVM and call Runtime::Start to start ART. In Runtime::Start function, libopenjdk.so is loaded by calling JavaVMExt::LoadNativeLibrary, and JavaVMExt::LoadNativeLibrary will invoke JNI_OnLoad of libopenjdk.so, JNI_OnLoad will invode JNIEnv::RegisterNatives to register JNI functions.
// Initialize the rest in the order in which they appear in Android.bp . register_java_util_zip_ZipFile(env); register_java_util_zip_Inflater(env); register_java_util_zip_Deflater(env); register_java_io_FileDescriptor(env); ... register_java_lang_Runtime(env); ... };
intjniRegisterNativeMethods(JNIEnv* env, constchar* className, const JNINativeMethod* methods, int numMethods) { ALOGV("Registering %s's %d native methods...", className, numMethods); jclass clazz = (*env)->FindClass(env, className); ALOG_ALWAYS_FATAL_IF(clazz == NULL, "Native registration unable to find class '%s'; aborting...", className); int result = (*env)->RegisterNatives(env, clazz, methods, numMethods); (*env)->DeleteLocalRef(env, clazz); if (result == 0) { return0; } ... }
3. Register JNI functions of Framework
In AndroidRuntime::start function, in addtion to starting JVM, android framework functions are registered by calling AndroidRuntime::startReg. Native C++ code of android framework is in libandroid_runtime.so and java code is in framework.jar and framework2.jar, Zygote relies on libandroid_runtime.so, but unlike other dynamic libraries, JNI functions registering of libandroid_runtime.so is not in JNI_OnLoad function. AndroidRuntime::startReg will invoke JNIEnv::RegisterNatives at last. Code Tracing as below:
Java classes are loaded to ART by ClassLoader of JRE(java Runtime Environment). There are three types of ClassLoader called Bootstrap Class Loader, Extension Class Loader, and Application Class Loader.
BootstrapClassLoader which written by C++ code is in ART, is responsible for loading the base class libraries, such as rt.jar, charset.jar etc., this Class Loader cannot be used directly by App. You can change class searching path by setting -bootclasspath parameter, or class will be loaded in JAVA_HOME/lib path.
ExtensionClassLoader and ApplicationClassLoader are written by java code and independent of ART, ExtensionClassLoader’s father class loader is BootstrapClassLoader, ApplicationClassLoader’s father class loader is ExtensionClassLoader. ExtensionClassLoader’s loading path is JAVA_HOME/lib/ext, which stored extension class libraries. ApplicationClassLoader’s loading path is CLASSPATH, which stored appilication’s java class libraries.
Class loading has two steps:
Check whether the class has been loaded, if it does, return loaded class immediately, searching direction is bottom to top.
If class has not been loaded, bottom ClassLoader will delegate his father ClassLoader to load class, so it is always topmost ClassLoader try to load firstly, in top to bottom order.
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { // First, check if the class has already been loaded Class<?> c = findLoadedClass(name); if (c == null) { try { if (parent != null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader }
if (c == null) { // If still not found, then invoke findClass in order // to find the class. c = findClass(name); } } return c; }
Statically register JNI functions
The first call to the Java native function triggers static registration if JNI functions haven’t been registerd dynamically. ART will assign JNI function address which exposed by dynamic library’s symbol table to JNI entry point of ARTMethod. After Class has been loaded by ClassLoader, ClassLinker helps to link ARTMethod’s entry point. Code tracing as below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
//Dynamic loading class and calling method by reflection ... Class<?> clazz = null; Objectinstance=null; try { clazz = Class.forName("com.example.android.simplejni.SimpleJNI"); instance = clazz.newInstance();
/** Called after security checks have been made. */ @FastNative staticnative Class<?> classForName(String className, boolean shouldInitialize, ClassLoader classLoader) throws ClassNotFoundException;
// We need to validate and convert the name (from x.y.z to x/y/z). This // is especially handy for array types, since we want to avoid // auto-generating bogus array classes. if (!IsValidBinaryClassName(name.c_str())) { soa.Self()->ThrowNewExceptionF("Ljava/lang/ClassNotFoundException;", "Invalid name: %s", name.c_str()); returnnullptr; }
ObjPtr<mirror::Class> ClassLinker::FindClass(Thread* self, constchar* descriptor, Handle<mirror::ClassLoader> class_loader){ ... // Class is not yet loaded. if (descriptor[0] != '[' && class_loader == nullptr) { // Non-array class and the boot class loader, search the boot class path. ClassPathEntry pair = FindInClassPath(descriptor, hash, boot_class_path_); if (pair.second != nullptr) { returnDefineClass(self, descriptor, hash, ScopedNullHandle<mirror::ClassLoader>(), *pair.first, *pair.second); } else { ... } } ... }
ObjPtr<mirror::Class> ClassLinker::DefineClass(Thread* self, constchar* descriptor, size_t hash, Handle<mirror::ClassLoader> class_loader, const DexFile& dex_file, const dex::ClassDef& dex_class_def) { ... // Get the real dex file. This will return the input if there aren't any callbacks or they do // nothing. DexFile const* new_dex_file = nullptr; dex::ClassDef const* new_class_def = nullptr; // TODO We should ideally figure out some way to move this after we get a lock on the klass so it // will only be called once. Runtime::Current()->GetRuntimeCallbacks()->ClassPreDefine(descriptor, klass, class_loader, dex_file, dex_class_def, &new_dex_file, &new_class_def); // Check to see if an exception happened during runtime callbacks. Return if so. if (self->IsExceptionPending()) { return sdc.Finish(nullptr); } ObjPtr<mirror::DexCache> dex_cache = RegisterDexFile(*new_dex_file, class_loader.Get()); if (dex_cache == nullptr) { self->AssertPendingException(); return sdc.Finish(nullptr); } klass->SetDexCache(dex_cache); SetupClass(*new_dex_file, *new_class_def, klass, class_loader.Get());
// Mark the string class by setting its access flag. if (UNLIKELY(!init_done_)) { if (strcmp(descriptor, "Ljava/lang/String;") == 0) { klass->SetStringClass(); } }
ObjectLock<mirror::Class> lock(self, klass); klass->SetClinitThreadId(self->GetTid()); // Make sure we have a valid empty iftable even if there are errors. klass->SetIfTable(GetClassRoot<mirror::Object>(this)->GetIfTable());
// Add the newly loaded class to the loaded classes table. ObjPtr<mirror::Class> existing = InsertClass(descriptor, klass.Get(), hash); if (existing != nullptr) { // We failed to insert because we raced with another thread. Calling EnsureResolved may cause // this thread to block. return sdc.Finish(EnsureResolved(self, descriptor, existing)); }
// Load the fields and other things after we are inserted in the table. This is so that we don't // end up allocating unfree-able linear alloc resources and then lose the race condition. The // other reason is that the field roots are only visited from the class table. So we need to be // inserted before we allocate / fill in these fields. LoadClass(self, *new_dex_file, *new_class_def, klass); ...
if (method->IsNative()) { // Set up the dlsym lookup stub. Do not go through `UnregisterNative()` // as the extra processing for @CriticalNative is not needed yet. method->SetEntryPointFromJni( method->IsCriticalNative() ? GetJniDlsymLookupCriticalStub() : GetJniDlsymLookupStub()); } }
// Used by the JNI dlsym stub to find the native method to invoke if none is registered. extern"C"constvoid* artFindNativeMethodRunnable(Thread* self) REQUIRES_SHARED(Locks::mutator_lock_){ ...
// Check whether we already have a registered native code. // For @CriticalNative it may not be stored in the ArtMethod as a JNI entrypoint if the class // was not visibly initialized yet. Do this check also for @FastNative and normal native for // consistency; though success would mean that another thread raced to do this lookup. constvoid* native_code = class_linker->GetRegisteredNative(self, method); if (native_code != nullptr) { return native_code; }
// Lookup symbol address for method, on failure we'll return null with an exception set, // otherwise we return the address of the method we found. JavaVMExt* vm = down_cast<JNIEnvExt*>(self->GetJniEnv())->GetVm(); std::string error_msg; native_code = vm->FindCodeForNativeMethod(method, &error_msg, /*can_suspend=*/true); if (native_code == nullptr) { LOG(ERROR) << error_msg; self->ThrowNewException("Ljava/lang/UnsatisfiedLinkError;", error_msg.c_str()); returnnullptr; }
// Register the code. This usually prevents future calls from coming to this function again. // We can still come here if the ClassLinker cannot set the entrypoint in the ArtMethod, // i.e. for @CriticalNative methods with the declaring class not visibly initialized. return class_linker->RegisterNative(self, method, native_code); }
constvoid* ClassLinker::RegisterNative( Thread* self, ArtMethod* method, constvoid* native_method){ CHECK(method->IsNative()) << method->PrettyMethod(); CHECK(native_method != nullptr) << method->PrettyMethod(); void* new_native_method = nullptr; Runtime* runtime = Runtime::Current(); runtime->GetRuntimeCallbacks()->RegisterNativeMethod(method, native_method, /*out*/&new_native_method); if (method->IsCriticalNative()) { MutexLock lock(self, critical_native_code_with_clinit_check_lock_); // Remove old registered method if any. auto it = critical_native_code_with_clinit_check_.find(method); if (it != critical_native_code_with_clinit_check_.end()) { critical_native_code_with_clinit_check_.erase(it); } // To ensure correct memory visibility, we need the class to be visibly // initialized before we can set the JNI entrypoint. if (method->GetDeclaringClass()->IsVisiblyInitialized()) { method->SetEntryPointFromJni(new_native_method); } else { critical_native_code_with_clinit_check_.emplace(method, new_native_method); } } else { method->SetEntryPointFromJni(new_native_method); } return new_native_method; }
voidSetEntryPointFromJni(constvoid* entrypoint) REQUIRES_SHARED(Locks::mutator_lock_){ // The resolution method also has a JNI entrypoint for direct calls from // compiled code to the JNI dlsym lookup stub for @CriticalNative. DCHECK(IsNative() || IsRuntimeMethod()); SetEntryPointFromJniPtrSize(entrypoint, kRuntimePointerSize); }
void* JavaVMExt::FindCodeForNativeMethod(ArtMethod* m, std::string* error_msg, bool can_suspend){ CHECK(m->IsNative()); ObjPtr<mirror::Class> c = m->GetDeclaringClass(); // If this is a static method, it could be called before the class has been initialized. CHECK(c->IsInitializing() || !NeedsClinitCheckBeforeCall(m)) << c->GetStatus() << " " << m->PrettyMethod(); Thread* const self = Thread::Current(); void* native_method = libraries_->FindNativeMethod(self, m, error_msg, can_suspend); if (native_method == nullptr && can_suspend) { // Lookup JNI native methods from native TI Agent libraries. See runtime/ti/agent.h for more // information. Agent libraries are searched for native methods after all jni libraries. native_method = FindCodeForNativeMethodInAgents(m); } return native_method; }
ART VM looks first for the short name; that is, the name without the argument signature. It then looks for the long name, which is the name with the argument signature. Programmers need to use the long name only when a native method is overloaded with another native method. However, this is not a problem if the native method has the same name as a nonnative method. A nonnative method (a Java method) does not reside in the native library.
eg short name: Com_example_android_simplejni_Native_add(JNIEnv *env, jobject thiz, jint a, jint b)
eg long name(apppend ‘__’ and argument signature): Com_example_android_simplejni_Native_add__II(JNIEnv *env, jobject thiz, jint a, jint b)
Dynamically register JNI functions
Native code accesses Java VM features by calling JNI functions. JNI functions are available through an interface pointer. An interface pointer is a pointer to a pointer. This pointer points to an array of pointers, each of which points to an interface function. Every interface function is at a predefined offset inside the array.
Important: JavaVM::GetEnv will return NULL if current thread is native thread, you should Call JavaVM::AttachCurrentThread to attach current native thread to ART VM and return JNIEnv pointer. Why do we access JVM by JNIEnv instead of JavaVM directly? Personally speaking, ART designers of JNI, who do not want to maintenance thread-dependent variables, so they expose JNIEnv structure. In fact, they can do it themselves based on the thread ID.
\libnativehelper\include_jni\jni.h
1 2 3 4 5
typedefstruct { constchar* name; // Native function name constchar* signature; // Argument signature of Native function void* fnPtr; // Native function Pointer } JNINativeMethod;
JNINativeMethod is a structure which contains the details of Java Native function, argument signature is to match arguments when a native method is overloaded.
classJNIEnvExt : public JNIEnv { public: // Creates a new JNIEnvExt. Returns null on error, in which case error_msg // will contain a description of the error. static JNIEnvExt* Create(Thread* self, JavaVMExt* vm, std::string* error_msg); ... };
const JNINativeInterface* GetJniNativeInterface(){ // The template argument is passed down through the Encode/DecodeArtMethod/Field calls so if // JniIdType is kPointer the calls will be a simple cast with no branches. This ensures that // the normal case is still fast. return Runtime::Current()->GetJniIdType() == JniIdType::kPointer ? &JniNativeInterfaceFunctions<false>::gJniNativeInterface : &JniNativeInterfaceFunctions<true>::gJniNativeInterface; }
static jint RegisterNatives(JNIEnv* env, jclass java_class, const JNINativeMethod* methods, jint method_count){ ... ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); ScopedObjectAccess soa(env); StackHandleScope<1> hs(soa.Self()); Handle<mirror::Class> c = hs.NewHandle(soa.Decode<mirror::Class>(java_class)); ... CHECK_NON_NULL_ARGUMENT_FN_NAME("RegisterNatives", methods, JNI_ERR); for (jint i = 0; i < method_count; ++i) { constchar* name = methods[i].name; constchar* sig = methods[i].signature; constvoid* fnPtr = methods[i].fnPtr; ... // Note: the right order is to try to find the method locally // first, either as a direct or a virtual method. Then move to // the parent. ArtMethod* m = nullptr; bool warn_on_going_to_parent = down_cast<JNIEnvExt*>(env)->GetVm()->IsCheckJniEnabled(); for (ObjPtr<mirror::Class> current_class = c.Get(); current_class != nullptr; current_class = current_class->GetSuperClass()) { // Search first only comparing methods which are native. m = FindMethod<true>(current_class, name, sig); if (m != nullptr) { break; }
// Search again comparing to all methods, to find non-native methods that match. m = FindMethod<false>(current_class, name, sig); if (m != nullptr) { break; } ... }
Dynamically JNI native functions registration is calling JNIEnv::RegisterNatives of current thread and then invokes JNIImpl::RegisterNatives which stored in the JNI function table and belongs to ART VM. JNIImpl::RegisterNatives function will register JNI Native function one by one, and will try super class if cannot find corresponding function name in current class method.
Java invoke native function
ART VM’s interpreter will invoke ArtInterpreterToCompiledCodeBridge function if ARTMethod ‘s entrypoint is compiled native code. ARTMethod’s entrypoint is entry_point_from_quick_compiled_code_, which is assigned to art_quick_generic_jni_trampoline if ARTMethod is native type function, art_quick_generic_jni_trampoline is a bridge function and finally invokes JNI native function.
voidArtInterpreterToCompiledCodeBridge(Thread* self, ArtMethod* caller, ShadowFrame* shadow_frame, uint16_t arg_offset, JValue* result) REQUIRES_SHARED(Locks::mutator_lock_){ ArtMethod* method = shadow_frame->GetMethod(); // Basic checks for the arg_offset. If there's no code item, the arg_offset must be 0. Otherwise, // check that the arg_offset isn't greater than the number of registers. A stronger check is // difficult since the frame may contain space for all the registers in the method, or only enough // space for the arguments. if (kIsDebugBuild) { if (method->GetCodeItem() == nullptr) { DCHECK_EQ(0u, arg_offset) << method->PrettyMethod(); } else { DCHECK_LE(arg_offset, shadow_frame->NumberOfVRegs()); } } jit::Jit* jit = Runtime::Current()->GetJit(); if (jit != nullptr && caller != nullptr) { jit->NotifyInterpreterToCompiledCodeTransition(self, caller); } method->Invoke(self, shadow_frame->GetVRegArgs(arg_offset), (shadow_frame->NumberOfVRegs() - arg_offset) * sizeof(uint32_t), result, method->GetInterfaceMethodIfProxy(kRuntimePointerSize)->GetShorty()); }
Runtime* runtime = Runtime::Current(); // Call the invoke stub, passing everything as arguments. // If the runtime is not yet started or it is required by the debugger, then perform the // Invocation by the interpreter, explicitly forcing interpretation over JIT to prevent // cycling around the various JIT/Interpreter methods that handle method invocation. if (UNLIKELY(!runtime->IsStarted() || (self->IsForceInterpreter() && !IsNative() && !IsProxyMethod() && IsInvokable()))) { ... } else { ...
// Initialize routine offsets to 0 for integers and floats. // x8 for integers, x15 for floating point. mov x8, #0 mov x15, #0
add x10, x5, #1// Load shorty address, plus one to skip return value. ldr w1, [x9],#4// Load "this" parameter, and increment arg pointer.
// Loop to fill registers. .LfillRegisters: ldrb w17, [x10], #1// Load next character in signature, and increment. cbz w17, .LcallFunction // Exit at end of signature. Shorty 0 terminated.
cmp w17, #'F'// is this a float? bne .LisDouble
cmp x15, # 8*12// Skip this load if all registers full. beq .Ladvance4
add x17, x13, x15 // Calculate subroutine to jump to. br x17
constvoid* quick_code = nullptr; if (oat_class != nullptr) { // Every kind of method should at least get an invoke stub from the oat_method. // non-abstract methods also get their code pointers. const OatFile::OatMethod oat_method = oat_class->GetOatMethod(class_def_method_index); quick_code = oat_method.GetQuickCode(); } runtime->GetInstrumentation()->InitializeMethodsCode(method, quick_code);
if (method->IsNative()) { // Set up the dlsym lookup stub. Do not go through `UnregisterNative()` // as the extra processing for @CriticalNative is not needed yet. method->SetEntryPointFromJni( method->IsCriticalNative() ? GetJniDlsymLookupCriticalStub() : GetJniDlsymLookupStub()); } }
> \art\runtime\instrumentation.cc ``` C++ voidInstrumentation::InitializeMethodsCode(ArtMethod* method, constvoid* aot_code) REQUIRES_SHARED(Locks::mutator_lock_){ ...
staticvoidUpdateEntryPoints(ArtMethod* method, constvoid* quick_code) REQUIRES_SHARED(Locks::mutator_lock_){ ... // If the method is from a boot image, don't dirty it if the entrypoint // doesn't change. if (method->GetEntryPointFromQuickCompiledCode() != quick_code) { method->SetEntryPointFromQuickCompiledCode(quick_code); } }
// Return the address of quick stub code for handling JNI calls. extern"C"voidart_quick_generic_jni_trampoline(ArtMethod*); staticinlineconstvoid* GetQuickGenericJniStub(){ returnreinterpret_cast<constvoid*>(art_quick_generic_jni_trampoline); }
3. Invoke native function tracing
\art\runtime\arch\arm64\quick_entrypoints_arm64.S
1 2 3 4 5 6 7 8 9 10 11 12 13
ENTRY art_quick_generic_jni_trampoline ... // prepare for artQuickGenericJniTrampoline call // (Thread*, managed_sp, reserved_area) // x0 x1 x2 <= C calling convention // xSELF xFP sp <= where they are
mov x0, xSELF // Thread* mov x1, xFP // SP for the managed frame. mov x2, sp// reserved area for arguments and other saved data (up to managed frame) bl artQuickGenericJniTrampoline // (Thread*, sp) ... END art_quick_generic_jni_trampoline
// Retrieve the stored native code. // Note that it may point to the lookup stub or trampoline. // FIXME: This is broken for @CriticalNative as the art_jni_dlsym_lookup_stub // does not handle that case. Calls from compiled stubs are also broken. voidconst* nativeCode = called->GetEntryPointFromJni();
ALWAYS_INLINE T GetNativePointer(MemberOffset offset, PointerSize pointer_size)const{ static_assert(std::is_pointer<T>::value, "T must be a pointer type"); constauto addr = reinterpret_cast<uintptr_t>(this) + offset.Uint32Value(); if (pointer_size == PointerSize::k32) { returnreinterpret_cast<T>(*reinterpret_cast<constuint32_t*>(addr)); } else { auto v = *reinterpret_cast<constuint64_t*>(addr); returnreinterpret_cast<T>(dchecked_integral_cast<uintptr_t>(v)); } }
ENTRY art_quick_generic_jni_trampoline ... // prepare for artQuickGenericJniTrampoline call // (Thread*, managed_sp, reserved_area) // x0 x1 x2 <= C calling convention // xSELF xFP sp <= where they are
mov x0, xSELF // Thread* mov x1, xFP // SP for the managed frame. mov x2, sp// reserved area for arguments and other saved data (up to managed frame) bl artQuickGenericJniTrampoline // (Thread*, sp)
// The C call will have registered the complete save-frame on success. // The result of the call is: // x0: pointer to native code, 0 on error. // The bottom of the reserved area contains values for arg registers, // hidden arg register and SP for out args for the call.
// Check for error (class init check or locking for synchronized native method can throw). cbz x0, .Lexception_in_native
// Load hidden arg (x15) for @CriticalNative and SP for out args. ldp x15, xIP1, [sp, #128]
// Apply the new SP for out args, releasing unneeded reserved area. movsp, xIP1
blr xIP0 // native call. ... END art_quick_generic_jni_trampoline
entry_point_from_quick_compiled_code_ is an attibute of ARTMethod, as a bridge function, it has three types:
If java function has been compiled to quick code, entry_point_from_quick_compiled_code_ is the adress of quick code.
If java function does not exist quick code, entry_point_from_quick_compiled_code_ is the adress of artQuickToInterpreterBridge, java byte code will be executed by ART interpreter.
If java native function does not exist quick code, entry_point_from_quick_compiled_code_ is the address of art_quick_generic_jni_trampoline, JNI native code will be called at last.
Native invoke Java function
For example, translating jstring to std::string, mainly has tow steps:
// Finds a class by its descriptor using the "system" class loader, ie by searching the // boot_class_path_. ObjPtr<mirror::Class> FindSystemClass(Thread* self, constchar* descriptor) REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!Locks::dex_lock_){ returnFindClass(self, descriptor, ScopedNullHandle<mirror::ClassLoader>()); }
ObjPtr<mirror::Class> ClassLinker::FindClass(Thread* self, constchar* descriptor, Handle<mirror::ClassLoader> class_loader){ DCHECK_NE(*descriptor, '\0') << "descriptor is empty string"; DCHECK(self != nullptr); self->AssertNoPendingException(); self->PoisonObjectPointers(); // For DefineClass, CreateArrayClass, etc... if (descriptor[1] == '\0') { // only the descriptors of primitive types should be 1 character long, also avoid class lookup // for primitive classes that aren't backed by dex files. returnFindPrimitiveClass(descriptor[0]); } constsize_t hash = ComputeModifiedUtf8Hash(descriptor); // Find the class in the loaded classes table. ObjPtr<mirror::Class> klass = LookupClass(self, descriptor, hash, class_loader.Get()); if (klass != nullptr) { returnEnsureResolved(self, descriptor, klass); } // Class is not yet loaded. if (descriptor[0] != '[' && class_loader == nullptr) { // Non-array class and the boot class loader, search the boot class path. ClassPathEntry pair = FindInClassPath(descriptor, hash, boot_class_path_); if (pair.second != nullptr) { returnDefineClass(self, descriptor, hash, ScopedNullHandle<mirror::ClassLoader>(), *pair.first, *pair.second); } else { // The boot class loader is searched ahead of the application class loader, failures are // expected and will be wrapped in a ClassNotFoundException. Use the pre-allocated error to // trigger the chaining with a proper stack trace. ObjPtr<mirror::Throwable> pre_allocated = Runtime::Current()->GetPreAllocatedNoClassDefFoundError(); self->SetException(pre_allocated); returnnullptr; } } ... }
Look up the class from the map in the current classLoader according to the descriptor, find the class in the method area, generate the class object and return. If class is not loaded yet, the bytecode will be loaded from dex firstly, then pushed to the method area via DefineClass to generate the class and return.
// Then search the superclass chain. If we find an inherited method, return it. // If we find a method that's not inherited because of access restrictions, // try to find a method inherited from an interface in copied methods. ObjPtr<Class> klass = this_klass->GetSuperClass(); ArtMethod* uninherited_method = nullptr; for (; klass != nullptr; klass = klass->GetSuperClass()) { DCHECK(!klass->IsProxyClass()); for (ArtMethod& method : klass->GetDeclaredMethodsSlice(pointer_size)) { if (method.GetNameView() == name && method.GetSignature() == signature) { if (IsInheritedMethod(this_klass, klass, method)) { return &method; } uninherited_method = &method; break; } } if (uninherited_method != nullptr) { break; } }
// Then search copied methods. // If we found a method that's not inherited, stop the search in its declaring class. ObjPtr<Class> end_klass = klass; DCHECK_EQ(uninherited_method != nullptr, end_klass != nullptr); klass = this_klass; if (UNLIKELY(klass->IsProxyClass())) { DCHECK(klass->GetCopiedMethodsSlice(pointer_size).empty()); klass = klass->GetSuperClass(); } for (; klass != end_klass; klass = klass->GetSuperClass()) { DCHECK(!klass->IsProxyClass()); for (ArtMethod& method : klass->GetCopiedMethodsSlice(pointer_size)) { if (method.GetNameView() == name && method.GetSignature() == signature) { return &method; // No further check needed, copied methods are inherited by definition. } } } return uninherited_method; // Return the `uninherited_method` if any. }
Class method searching is similar to dynamically native function registration, should match both method name and parameters signature. If searching missed, superclass chain and copied methods will be tried.
template <> JValue InvokeVirtualOrInterfaceWithVarArgs(const ScopedObjectAccessAlreadyRunnable& soa, jobject obj, ArtMethod* interface_method, va_list args){ // We want to make sure that the stack is not within a small distance from the // protected region in case we are calling into a leaf function whose stack // check has been elided. if (UNLIKELY(__builtin_frame_address(0) < soa.Self()->GetStackEnd())) { ThrowStackOverflowError(soa.Self()); returnJValue(); }
voidArtMethod::Invoke(Thread* self, uint32_t* args, uint32_t args_size, JValue* result, constchar* shorty){ ... Runtime* runtime = Runtime::Current(); // Call the invoke stub, passing everything as arguments. // If the runtime is not yet started or it is required by the debugger, then perform the // Invocation by the interpreter, explicitly forcing interpretation over JIT to prevent // cycling around the various JIT/Interpreter methods that handle method invocation. if (UNLIKELY(!runtime->IsStarted() || (self->IsForceInterpreter() && !IsNative() && !IsProxyMethod() && IsInvokable()))) { if (IsStatic()) { art::interpreter::EnterInterpreterFromInvoke( self, this, nullptr, args, result, /*stay_in_interpreter=*/true); } else { mirror::Object* receiver = reinterpret_cast<StackReference<mirror::Object>*>(&args[0])->AsMirrorPtr(); art::interpreter::EnterInterpreterFromInvoke( self, this, receiver, args + 1, result, /*stay_in_interpreter=*/true); } } else { DCHECK_EQ(runtime->GetClassLinker()->GetImagePointerSize(), kRuntimePointerSize);
// Initialize routine offsets to 0 for integers and floats. // x8 for integers, x15 for floating point. mov x8, #0 mov x15, #0
add x10, x5, #1// Load shorty address, plus one to skip return value. ldr w1, [x9],#4// Load "this" parameter, and increment arg pointer.
// Loop to fill registers. .LfillRegisters: ldrb w17, [x10], #1// Load next character in signature, and increment. cbz w17, .LcallFunction // Exit at end of signature. Shorty 0 terminated.
cmp w17, #'F'// is this a float? bne .LisDouble
cmp x15, # 8*12// Skip this load if all registers full. beq .Ladvance4
add x17, x13, x15 // Calculate subroutine to jump to. br x17
finally entry_point_from_quick_compiled_code_ function will be called, entry_point_from_quick_compiled_code_ is a bridge, it will be assigned to art_quick_to_interpreter_bridge when class is loaded if it has no quit code.
constvoid* quick_code = nullptr; if (oat_class != nullptr) { // Every kind of method should at least get an invoke stub from the oat_method. // non-abstract methods also get their code pointers. const OatFile::OatMethod oat_method = oat_class->GetOatMethod(class_def_method_index); quick_code = oat_method.GetQuickCode(); } runtime->GetInstrumentation()->InitializeMethodsCode(method, quick_code);
if (method->IsNative()) { // Set up the dlsym lookup stub. Do not go through `UnregisterNative()` // as the extra processing for @CriticalNative is not needed yet. method->SetEntryPointFromJni( method->IsCriticalNative() ? GetJniDlsymLookupCriticalStub() : GetJniDlsymLookupStub()); } }
// Special case if we need an initialization check. if (NeedsClinitCheckBeforeCall(method) && !method->GetDeclaringClass()->IsVisiblyInitialized()) { // If we have code but the method needs a class initialization check before calling // that code, install the resolution stub that will perform the check. // It will be replaced by the proper entry point by ClassLinker::FixupStaticTrampolines // after initializing class (see ClassLinker::InitializeClass method). // Note: this mimics the logic in image_writer.cc that installs the resolution // stub only if we have compiled code or we can execute nterp, and the method needs a class // initialization check. if (aot_code != nullptr || method->IsNative() || CanUseNterp(method)) { if (kIsDebugBuild && CanUseNterp(method)) { // Adds some test coverage for the nterp clinit entrypoint. UpdateEntryPoints(method, interpreter::GetNterpWithClinitEntryPoint()); } else { UpdateEntryPoints(method, GetQuickResolutionStub()); } } else { UpdateEntryPoints(method, GetQuickToInterpreterBridge()); } return; }
// Use the provided AOT code if possible. if (CanUseAotCode(aot_code)) { UpdateEntryPoints(method, aot_code); return; }
// We check if the class is verified as we need the slow interpreter for lock verification. // If the class is not verified, This will be updated in // ClassLinker::UpdateClassAfterVerification. if (CanUseNterp(method)) { UpdateEntryPoints(method, interpreter::GetNterpEntryPoint()); return; }
staticvoidUpdateEntryPoints(ArtMethod* method, constvoid* quick_code) REQUIRES_SHARED(Locks::mutator_lock_){ ... // If the method is from a boot image, don't dirty it if the entrypoint // doesn't change. if (method->GetEntryPointFromQuickCompiledCode() != quick_code) { method->SetEntryPointFromQuickCompiledCode(quick_code); } }
// Return the address of quick stub code for bridging from quick code to the interpreter. extern"C"voidart_quick_to_interpreter_bridge(ArtMethod*); staticinlineconstvoid* GetQuickToInterpreterBridge(){ returnreinterpret_cast<constvoid*>(art_quick_to_interpreter_bridge); }
LinkCode will judge method type when initialize method code, if method has been compiled to aot code, ART execute quick code directly, or ART will interpret java bytecode. We assume that method is not optimized to quick code, let’s tracing following interpreter code.
4. Invoke art_quick_to_interpreter_bridge
C++ thread’s stack, regiters and PC(Program Counter) are managed by the C++ compiler, but to JVM, should manage these function calling states. ART interpreter has his own calling stack named ShadowFrame which contains function calling context. Java Function’s instructions will be execute one by one refer to DEX_INSTRUCTION_LIST smali table, instructions like “invoke-virtual”, “invoke-static”, “invoke-direct” etc. these instructions will invoke new methods calling, and start new ART calling cycles(lower efficient than native code).
\art\runtime\arch\arm64\quick_entrypoints_arm64.S
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
ENTRY art_quick_to_interpreter_bridge SETUP_SAVE_REFS_AND_ARGS_FRAME // Set up frame and save arguments.
// x0 will contain mirror::ArtMethod* method. mov x1, xSELF // How to get Thread::Current() ??? mov x2, sp
if (LIKELY(!from_deoptimize)) { // Entering the method, but not via deoptimization. ... ArtMethod *method = shadow_frame.GetMethod();
// If we can continue in JIT and have JITed code available execute JITed code. if (!stay_in_interpreter && !self->IsForceInterpreter() && !shadow_frame.GetForcePopFrame()) { jit::Jit* jit = Runtime::Current()->GetJit(); if (jit != nullptr) { jit->MethodEntered(self, shadow_frame.GetMethod()); if (jit->CanInvokeCompiledCode(method)) { JValue result;
// Pop the shadow frame before calling into compiled code. self->PopShadowFrame(); // Calculate the offset of the first input reg. The input registers are in the high regs. // It's ok to access the code item here since JIT code will have been touched by the // interpreter and compiler already. uint16_t arg_offset = accessor.RegistersSize() - accessor.InsSize(); ArtInterpreterToCompiledCodeBridge(self, nullptr, &shadow_frame, arg_offset, &result); // Push the shadow frame back as the caller will expect it. self->PushShadowFrame(&shadow_frame);
// TODO: find a cleaner way to separate non-range and range information without duplicating // code. uint32_t arg[Instruction::kMaxVarArgRegs] = {}; // only used in invoke-XXX. uint32_t vregC = 0; if (is_range) { vregC = inst->VRegC_3rc(); } else { vregC = inst->VRegC_35c(); inst->GetVarArgs(arg, inst_data); }
classJNIEnvExt : public JNIEnv { public: // Creates a new JNIEnvExt. Returns null on error, in which case error_msg // will contain a description of the error. static JNIEnvExt* Create(Thread* self, JavaVMExt* vm, std::string* error_msg); ... };
[>\art\runtime\jni\jni_internal.cc] const JNINativeInterface* GetJniNativeInterface(){ // The template argument is passed down through the Encode/DecodeArtMethod/Field calls so if // JniIdType is kPointer the calls will be a simple cast with no branches. This ensures that // the normal case is still fast. return Runtime::Current()->GetJniIdType() == JniIdType::kPointer ? &JniNativeInterfaceFunctions<false>::gJniNativeInterface : &JniNativeInterfaceFunctions<true>::gJniNativeInterface; }
classJNIEnvExt : public JNIEnv { ... // Link to Thread::Current(). Thread* const self_;
// The invocation interface JavaVM. JavaVMExt* const vm_;
// Cookie used when using the local indirect reference table. IRTSegmentState local_ref_cookie_;
// JNI local references. IndirectReferenceTable locals_ GUARDED_BY(Locks::mutator_lock_);
// Stack of cookies corresponding to PushLocalFrame/PopLocalFrame calls. // TODO: to avoid leaks (and bugs), we need to clear this vector on entry (or return) // to a native method. std::vector<IRTSegmentState> stacked_local_ref_cookies_;
// Entered JNI monitors, for bulk exit on thread detach. ReferenceTable monitors_; ... };
JNIEnv is associated to Thread, for Android application, there are two kinds of threads, Java Thread and Native Thread.
Java Thread JNIEnvEx is created during Java thread initialization, and released when Java thread Exit.
Call AttachCurrentThread to attach current native thread to JVM, and create associated Thread and JNIEnvEx object. Call DetachCurrentThread to detach current native thread from JVM, and release associated Thread and JNIEnv object.
voidRuntime::DetachCurrentThread(bool should_run_callbacks){ ScopedTrace trace(__FUNCTION__); Thread* self = Thread::Current(); if (self == nullptr) { LOG(FATAL) << "attempting to detach thread that is not attached"; } if (self->HasManagedStack()) { LOG(FATAL) << *Thread::Current() << " attempting to detach while still running code"; } thread_list_->Unregister(self, should_run_callbacks); }
Local reference talbe is resizable(Global reference talbe size is not resizable), default size is 64, table size will be double when table is full, this value is fixed to 512 in old version of Android System, if overflow, application will abort and print log “JNI ERROR (app bug): local reference table overflow (max=512)”.
\art\runtime\jni\jni_internal.cc
1 2 3 4 5 6 7 8 9
static jobject NewLocalRef(JNIEnv* env, jobject obj){ ScopedObjectAccess soa(env); ObjPtr<mirror::Object> decoded_obj = soa.Decode<mirror::Object>(obj); // Check for null after decoding the object to handle cleared weak globals. if (decoded_obj == nullptr) { returnnullptr; } return soa.AddLocalReference<jobject>(decoded_obj); }
\art\runtime\jni\jni_env_ext-inl.h
1 2 3 4 5 6 7 8 9 10 11
inline T JNIEnvExt::AddLocalReference(ObjPtr<mirror::Object> obj){ std::string error_msg; IndirectRef ref = locals_.Add(local_ref_cookie_, obj, &error_msg); if (UNLIKELY(ref == nullptr)) { // This is really unexpected if we allow resizing local IRTs... LOG(FATAL) << error_msg; UNREACHABLE(); } ... returnreinterpret_cast<T>(ref); }
// We know there's enough room in the table. Now we just need to find // the right spot. If there's a hole, find it and fill it; otherwise, // add to the end of the list. IndirectRef result; size_t index; if (current_num_holes_ > 0) { DCHECK_GT(top_index, 1U); // Find the first hole; likely to be near the end of the list. IrtEntry* p_scan = &table_[top_index - 1]; DCHECK(!p_scan->GetReference()->IsNull()); --p_scan; while (!p_scan->GetReference()->IsNull()) { DCHECK_GE(p_scan, table_ + previous_state.top_index); --p_scan; } index = p_scan - table_; current_num_holes_--; } else { // Add to the end. index = top_index++; segment_state_.top_index = top_index; } table_[index].Add(obj); result = ToIndirectRef(index); if (kDebugIRT) { LOG(INFO) << "+++ added at " << ExtractIndex(result) << " top=" << segment_state_.top_index << " holes=" << current_num_holes_; }
DCHECK(result != nullptr); return result; }
Local Reference Table Size
sizeof(IrtEntry) is 8, uint32_t and GcRoot< mirror::Object > are both 4 bytes size, GcRoot< mirror::Object > is a reference counter, finally defined a 4 byte int. So kSmallIrtEntries = kInitialIrtBytes / sizeof(IrtEntry) = 512/8 = 64. Refer to JNIEnvExt’s construction function, max_count is 1, infer that default size is kSmallIrtEntries.
You know, android is popular all over the world, many phone brands use Android OS, or modified version. If a phone has a large memory, phone brand can defaultly set a larger value, for example 128, as the logic of IndirectReferenceTable’s construction, at last, table size will be set RoundUp(128,4096/8)=512, So, you will find the design, 64 or 512 or more, (64x2x2x2) * sizeof(IrtEntry) = PAGESIZE = 4096, to reduce cross-page access memory.
constexprsize_t kInitialIrtBytes = 512; // Number of bytes in an initial local table. constexprsize_t kSmallIrtEntries = kInitialIrtBytes / sizeof(IrtEntry); ... classIrtEntry { public: voidAdd(ObjPtr<mirror::Object> obj)REQUIRES_SHARED(Locks::mutator_lock_);
private: uint32_t serial_; // Incremented for each reuse; checked against reference. GcRoot<mirror::Object> reference_; };
classIndirectReferenceTable { ... // Max_count is the minimum initial capacity (resizable), or minimum total capacity // (not resizable). A value of 1 indicates an implementation-convenient small size. IndirectReferenceTable(size_t max_count, IndirectRefKind kind, ResizableCapacity resizable, std::string* error_msg);
~IndirectReferenceTable(); ... /// semi-public - read/write by jni down calls. IRTSegmentState segment_state_;
// Mem map where we store the indirect refs. If it's invalid, and table_ is non-null, then // table_ is valid, but was allocated via allocSmallIRT(); MemMap table_mem_map_; // bottom of the stack. Do not directly access the object references // in this as they are roots. Use Get() that has a read barrier. IrtEntry* table_; // bit mask, ORed into all irefs. const IndirectRefKind kind_;
// System page size. We check this against sysconf(_SC_PAGE_SIZE) at runtime, but use a simple // compile-time constant so the compiler can generate better code. staticconstexprsize_t kPageSize = 4096;
Local references will be released when Native thread detach from JVM or Java thread exit. Local references will not be released if creating local reference frequently and donot delete local reference proactively in a resident thread.
staticvoidDeleteLocalRef(JNIEnv* env, jobject obj){ if (obj == nullptr) { return; } // SOA is only necessary to have exclusion between GC root marking and removing. // We don't want to have the GC attempt to mark a null root if we just removed // it. b/22119403 ScopedObjectAccess soa(env); auto* ext_env = down_cast<JNIEnvExt*>(env); if (!ext_env->locals_.Remove(ext_env->local_ref_cookie_, obj)) { // Attempting to delete a local reference that is not in the // topmost local reference frame is a no-op. DeleteLocalRef returns // void and doesn't throw any exceptions, but we should probably // complain about it so the user will notice that things aren't // going quite the way they expect. LOG(WARNING) << "JNI WARNING: DeleteLocalRef(" << obj << ") " << "failed to find entry"; // Investigating b/228295454: Scudo ERROR: internal map failure (NO MEMORY). soa.Self()->DumpJavaStack(LOG_STREAM(WARNING)); } }
Global reference
Global reference is keeped in JVM, one process only support one JVM, so global reference table is globally unique resource.
\art\runtime\jni\java_vm_ext.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
classJavaVMExt : public JavaVM { ... // Not guarded by globals_lock since we sometimes use SynchronizedGet in Thread::DecodeJObject. IndirectReferenceTable globals_;
// No lock annotation since UnloadNativeLibraries is called on libraries_ but locks the // jni_libraries_lock_ internally. std::unique_ptr<Libraries> libraries_;
// Used by -Xcheck:jni. const JNIInvokeInterface* const unchecked_functions_;
// Since weak_globals_ contain weak roots, be careful not to // directly access the object references in it. Use Get() with the // read barrier enabled. // Not guarded by weak_globals_lock since we may use SynchronizedGet in DecodeWeakGlobal. IndirectReferenceTable weak_globals_; ... };
And default size is 51200, not resizable. But GC will release weak global reference when memory is out of supply.
jobject JavaVMExt::AddGlobalRef(Thread* self, ObjPtr<mirror::Object> obj){ // Check for null after decoding the object to handle cleared weak globals. if (obj == nullptr) { returnnullptr; } IndirectRef ref; std::string error_msg; { WriterMutexLock mu(self, *Locks::jni_globals_lock_); ref = globals_.Add(kIRTFirstSegment, obj, &error_msg); MaybeTraceGlobals(); } if (UNLIKELY(ref == nullptr)) { LOG(FATAL) << error_msg; UNREACHABLE(); } CheckGlobalRefAllocationTracking(); returnreinterpret_cast<jobject>(ref); }
jweak JavaVMExt::AddWeakGlobalRef(Thread* self, ObjPtr<mirror::Object> obj){ if (obj == nullptr) { returnnullptr; } MutexLock mu(self, *Locks::jni_weak_globals_lock_); // CMS needs this to block for concurrent reference processing because an object allocated during // the GC won't be marked and concurrent reference processing would incorrectly clear the JNI weak // ref. But CC (gUseReadBarrier == true) doesn't because of the to-space invariant. if (!gUseReadBarrier) { WaitForWeakGlobalsAccess(self); } std::string error_msg; IndirectRef ref = weak_globals_.Add(kIRTFirstSegment, obj, &error_msg); MaybeTraceWeakGlobals(); if (UNLIKELY(ref == nullptr)) { LOG(FATAL) << error_msg; UNREACHABLE(); } returnreinterpret_cast<jweak>(ref); }
1.Frequently Calling AttachCurrentThread and DetachCurrentThread is not recommended, low efficient and not necessary.
Threads attached through JNI must call DetachCurrentThread() before they exit. If coding this directly is awkward, in Android 2.0 (Eclair) and higher you can use pthread_key_create() to define a destructor function that will be called before the thread exits, and call DetachCurrentThread() from there. (Use that key with pthread_setspecific() to store the JNIEnv in thread-local-storage; that way it’ll be passed into your destructor as the argument.)
Note that this mechanism of exiting the thread and then calling the DetachCurrentThread causes a delay in releasing the Local reference, you’d better release local reference by calling DeleteLocalRef.
2.Avoid calls between java and C++, it’s inefficient, if you have to, there are ways to optimize. for example, 1) Cache method ids, field ids, and classes. 2)Avoid Java->C++->Java.